#include "CursorClipper.hpp"
#include "GamingToolException.hpp"
#include "include/CursorClipper.hpp"
#include "include/GamingToolException.hpp"
#include <Windows.h>
#include <cassert>
#include <cstddef>
#include <cstdio>
#include <errhandlingapi.h>
#include <functional>
#include <memory.h>
#include <processthreadsapi.h>
#include <stack>
#include <cwchar>
#include <string>
#include <memory>
#include <unordered_map>

#ifdef __cplusplus
extern "C" \
{
#endif
#define WINDOW_RECT_MUTEX_NAME_STRING L"g_GamingToolWindowRectMutex"
#define MAX_WINDOW_TEXT_LENGTH (512)
#define MAX_ERROR_MESSAGE_LENGTH (512)
#define LENGTHOF(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0]))

std::stack<HWND>* g_lpWndStack;
std::unordered_map<std::wstring, LONG_PTR> g_OriginalWindowStyle;
// std::unordered_map<std::wstring, std::shared_ptr<WINDOWINFO>>* g_lpBorderlessWindowMap;

BOOL DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    return TRUE;
}

HANDLE g_hMouseClipper;
BOOL g_bMouseClipperNeeded;
HANDLE g_hWindowRectMutex;
WCHAR g_wWindowToClipText[MAX_WINDOW_TEXT_LENGTH];
RECT g_WndClipBorder;
WCHAR g_wLastErrorMessage[MAX_ERROR_MESSAGE_LENGTH];

static VOID AsyncPlaySoundW(LPCWSTR lpszSound) noexcept;
static VOID AdjustWindowRectToMiddleOfScreen(RECT& rect) noexcept;
__declspec(dllexport) VOID ToggleTray() noexcept;
__declspec(dllexport) BOOL MinimizeForegroundWindow();
__declspec(dllexport) BOOL RestoreMinimizedWindow() noexcept;
__declspec(dllexport) VOID ToggleCursorClipper() noexcept;
__declspec(dllexport) VOID MakeForegroundWindowBorderless() noexcept;
__declspec(dllexport) VOID ChangeForegroundWindowInputLanguage() noexcept;
__declspec(dllexport) BOOL InitializeGamingToolDll();
__declspec(dllexport) VOID DeinitializeGamingToolDll();

__declspec(dllexport) BOOL InitializeGamingToolDll()
{
    g_lpWndStack = new std::stack<HWND>();
    // g_lpBorderlessWindowMap = new std::unordered_map<std::wstring, std::shared_ptr<WINDOWINFO>>();
    return g_lpWndStack != nullptr;
}

__declspec(dllexport) VOID DeinitializeGamingToolDll()
{
    delete g_lpWndStack;
    // delete g_lpBorderlessWindowMap;
    CursorClipper::close();
}

__declspec(dllexport) VOID ToggleTray() noexcept
{
    HWND hTray = FindWindowW(L"Shell_TrayWnd", NULL);
    if (IsWindowVisible(hTray))
    {
        ShowWindow(hTray, SW_HIDE);
    }
    else
    {
        ShowWindow(hTray, SW_SHOWNORMAL);
    }
}

__declspec(dllexport) VOID HideTray() noexcept
{
    HWND hTray = FindWindowW(L"Shell_TrayWnd", NULL);
    ShowWindow(hTray, SW_HIDE);
}

__declspec(dllexport) VOID ShowTray() noexcept
{
    HWND hTray = FindWindowW(L"Shell_TrayWnd", NULL);
    ShowWindow(hTray, SW_SHOWNORMAL);
}

__declspec(dllexport) BOOL MinimizeForegroundWindow()
{
    HWND hForeWnd = GetForegroundWindow();
    if (hForeWnd)
    {
        g_lpWndStack->push(hForeWnd);
        ShowWindow(hForeWnd, SW_MINIMIZE);
        return TRUE;
    }
    return FALSE;
}

__declspec(dllexport) BOOL RestoreMinimizedWindow() noexcept
{
    if (g_lpWndStack->empty())
    {
        return FALSE;
    }
    HWND hWnd = g_lpWndStack->top();
    g_lpWndStack->pop();
    if (IsWindow(hWnd))
    {
        ShowWindow(hWnd, SW_RESTORE);
        return TRUE;
    }
    return FALSE;
}

__declspec(dllexport) VOID ToggleCursorClipper() noexcept
{
    if (CursorClipper::is_alive())
    {
        CursorClipper::close();
        AsyncPlaySoundW(L"C:\\Windows\\Media\\Windows Print Complete.wav");
        return;
    }
    try
    {
        CursorClipper::open();
        AsyncPlaySoundW(L"C:\\Windows\\Media\\Windows Notify.wav");
    }
    catch (GamingToolException e)
    {
        e.notify();
    }
}

__declspec(dllexport) VOID MakeWindowBorderless(HWND hWnd) noexcept
{
    std::wstring window_text;
    int window_text_length;
    DWORD dwOriginalStyle;
    DWORD dwModifiedStyle;
    RECT rcWindow;
    if (!IsWindow(hWnd) || 0 == (dwOriginalStyle = GetWindowLongW(hWnd, GWL_STYLE)) || !GetClientRect(hWnd, &rcWindow) || !(window_text_length = GetWindowTextLengthW(hWnd)))
    {
        return;
    }
    window_text.resize(window_text_length);
    if (g_OriginalWindowStyle.find(window_text) == g_OriginalWindowStyle.end())
    {
        g_OriginalWindowStyle[std::move(window_text)] = dwOriginalStyle;
    }
    dwModifiedStyle = dwOriginalStyle & ~(WS_CAPTION | WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX); // 去除边框后的窗口样式
    RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ERASENOW | RDW_INTERNALPAINT);
    Sleep(500);
    AdjustWindowRect(&rcWindow, dwModifiedStyle, FALSE);
    AdjustWindowRectToMiddleOfScreen(rcWindow);
    SetWindowLongPtrW(hWnd, GWL_STYLE, dwModifiedStyle);
    Sleep(500);
    SetWindowPos(hWnd, 0,  rcWindow.left, rcWindow.top, rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top, SWP_SHOWWINDOW | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOSENDCHANGING);
    Sleep(500);
    RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ERASENOW | RDW_INTERNALPAINT);
}

__declspec(dllexport) VOID MakeForegroundWindowBorderless() noexcept
{
    HWND hWnd = GetForegroundWindow();
    std::wstring window_text;
    int window_text_length;
    DWORD dwOriginalStyle;
    DWORD dwModifiedStyle;
    RECT rcWindow;
    try
    {
        if (!IsWindow(hWnd) || 0 == (dwOriginalStyle = GetWindowLongW(hWnd, GWL_STYLE)) || !GetClientRect(hWnd, &rcWindow) || !(window_text_length = GetWindowTextLengthW(hWnd)))
        {
            throw GamingToolException(
                "错误描述：获取前台窗口信息失败。\r\n"
                
                "错误代码：%lu。\r\n",

                GetLastError()
            );
        }
        window_text.resize(window_text_length);
        if (!GetWindowTextW(hWnd, &window_text[0], window_text_length)) // 不在表中
        {
            throw GamingToolException(
                "错误描述：无法读取窗口名称。\r\n"
                "错误代码：%lu。\r\n",
                GetLastError()
            );
        }
        if (g_OriginalWindowStyle.find(window_text) == g_OriginalWindowStyle.end())
        {
            g_OriginalWindowStyle[std::move(window_text)] = dwOriginalStyle;
        }
        dwModifiedStyle = dwOriginalStyle & ~(WS_CAPTION | WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | RDW_INTERNALPAINT); // 去除边框后的窗口样式
    }
    catch (GamingToolException e)
    {
        e.notify();
        return;
    }
    
    RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ERASENOW | RDW_INTERNALPAINT);
    Sleep(500);
    AdjustWindowRect(&rcWindow, dwModifiedStyle, FALSE);
    AdjustWindowRectToMiddleOfScreen(rcWindow);
    SetWindowLongPtrW(hWnd, GWL_STYLE, dwModifiedStyle);
    Sleep(500);
    SetWindowPos(hWnd, 0,  rcWindow.left, rcWindow.top, rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top, SWP_SHOWWINDOW | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOSENDCHANGING);
    Sleep(500);
    RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ERASENOW | RDW_INTERNALPAINT);
}

__declspec(dllexport) VOID MakeForegroundWindowBordered() noexcept
{
    HWND hWnd = GetForegroundWindow();
    std::wstring window_text;
    int window_text_length;
    DWORD dwCurrentStyle;
    DWORD dwOriginalStyle;
    RECT rcWindow;
    try
    {   
        if (!IsWindow(hWnd) || 0 == (dwCurrentStyle = GetWindowLongW(hWnd, GWL_STYLE)) || !GetClientRect(hWnd, &rcWindow) || !(window_text_length = GetWindowTextLengthW(hWnd)))
        {
            throw GamingToolException(
                "错误描述：获取前台窗口信息失败。\r\n"
                
                "错误代码：%lu。\r\n",

                GetLastError()
            );
        }
        window_text.resize(window_text_length);
        if (!GetWindowTextW(hWnd, &window_text[0], window_text_length))
        {
            throw GamingToolException(
                "错误描述：无法读取窗口名称。\r\n"
                "错误代码：%lu。\r\n",
                GetLastError()
            );
        }
        if (g_OriginalWindowStyle.find(window_text) == g_OriginalWindowStyle.end())
        {
            throw GamingToolException(
                "错误描述：找不到有关此窗口的信息（尚未去除过此窗口边框）。\r\n"
                "错误代码：%lu。\r\n",
                GetLastError()
            );
        }
        dwOriginalStyle = g_OriginalWindowStyle[window_text]; // 原来的样式
    }
    catch (GamingToolException e)
    {
        e.notify();
        return;
    }

    if (dwOriginalStyle != WS_OVERLAPPED)
    {
        AdjustWindowRect(&rcWindow, dwOriginalStyle | dwCurrentStyle, FALSE);
    }
    RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ERASENOW | RDW_INTERNALPAINT);
    Sleep(500);
    AdjustWindowRectToMiddleOfScreen(rcWindow);
    SetWindowLongPtrW(hWnd, GWL_STYLE, dwOriginalStyle | dwCurrentStyle);
    Sleep(500);
    SetWindowPos(hWnd, 0,  rcWindow.left, rcWindow.top, rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top, SWP_SHOWWINDOW | SWP_NOZORDER | SWP_NOSENDCHANGING);
    Sleep(500);
    InvalidateRect(hWnd, NULL, TRUE);
    RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ERASENOW);
}

__declspec(dllexport) VOID ToggleTopmostForegroundWindow() noexcept
{
    HWND hForeWnd = GetForegroundWindow();
    if (!hForeWnd) return;
    LONG_PTR lWindow = GetWindowLongPtrW(hForeWnd, GWL_EXSTYLE);
    if ((lWindow & WS_EX_TOPMOST) == 0)
    {
        SetWindowPos(hForeWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
        AsyncPlaySoundW(L"C:\\Windows\\Media\\Speech On.wav");
    }
    else /* 已经置顶 */
    {
        SetWindowPos(hForeWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
        AsyncPlaySoundW(L"C:\\Windows\\Media\\Speech Off.wav");
    }
}

__declspec(dllexport) VOID TopmostWindow(HWND hWnd) noexcept
{
    if (!IsWindow(hWnd)) return;
    LONG_PTR lWindow = GetWindowLongPtrW(hWnd, GWL_EXSTYLE);
    SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
        AsyncPlaySoundW(L"C:\\Windows\\Media\\Speech On.wav");
}

__declspec(dllexport) VOID UntopmostWindow(HWND hWnd) noexcept
{
    if (!IsWindow(hWnd)) return;
    LONG_PTR lWindow = GetWindowLongPtrW(hWnd, GWL_EXSTYLE);
    SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
    AsyncPlaySoundW(L"C:\\Windows\\Media\\Speech Off.wav");
}

__declspec(dllexport) VOID ChangeForegroundWindowInputLanguage() noexcept
{
    HWND hForeWnd = GetForegroundWindow();
    if (!hForeWnd) return;
    DWORD dwTId = GetWindowThreadProcessId(hForeWnd, NULL);
    HKL current_hkl = GetKeyboardLayout(dwTId);
    int nBuff = GetKeyboardLayoutList(0, NULL);
    LPHKL hkl_list = new HKL[nBuff];
    int nCount = GetKeyboardLayoutList(nBuff, hkl_list);
    /* 在列表中查找 current_hkl */
    int i;
    for (i = 0; i < nCount; i++)
    {
        if (hkl_list[i] == current_hkl)
        {
            break;
        }
    }
    i = (i + 1) % nCount;
    PostMessageW(hForeWnd, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)hkl_list[i]);
    delete[] hkl_list;
}

VOID AsyncPlaySoundW(LPCWSTR lpszSound) noexcept
{
	HMODULE hMod = LoadLibraryW(L"Winmm.dll");
	if (!hMod) return;
	auto PlaySoundW = (BOOL(*)(_In_opt_ LPCWSTR, _In_opt_ HMODULE, _In_ DWORD))GetProcAddress(hMod, "PlaySoundW");
	PlaySoundW(lpszSound, NULL, SND_ASYNC);
	FreeLibrary(hMod);
}

VOID AdjustWindowRectToMiddleOfScreen(RECT& rect) noexcept
{
    RECT rcScreen = {
        .left = 0,
        .top = 0
    };
    rcScreen.right = GetSystemMetrics(SM_CXSCREEN);
    rcScreen.bottom = GetSystemMetrics(SM_CYSCREEN);
    LONG lDeltaX = (rcScreen.right - (rect.left + rect.right)) / 2;
    LONG lDeltaY = (rcScreen.bottom - (rect.top + rect.bottom)) / 2;
    rect.left += lDeltaX;
    rect.right += lDeltaX;
    rect.top += lDeltaY;
    rect.bottom += lDeltaY;
}

#ifdef __cplusplus
}
#endif
